/*
 * Copyright (c) 2007, 2008 fr3@K <freak@fsfoundry.org>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef MANKA__ARENA_ALLOCATOR_HPP
#define MANKA__ARENA_ALLOCATOR_HPP

#include <manka/nullptr.hpp>

#if defined(MANKA_HAS_BOOST)
#include <boost/utility.hpp> // boost::addressof()
#endif // MANKA_HAS_BOOST

#include <cstddef>
#include <new> // std::bad_alloc

namespace manka
{

#if !defined(MANKA_DOC)
    namespace bits
    {
#if defined(MANKA_HAS_BOOST)
        using boost::addressof;
#else        
        template <class T>
        T* addressof(T& t)
        {
            return &t;
        }
#endif // MANKA_HAS_BOOST
    } // namespace bits
#endif // !MANKA_DOC

    /** A memory arena to standard allocator adapter.
     *
     */
    template <
        typename ArenaType,
        typename T>
    class arena_allocator;
    

    template <typename ArenaType>
    class arena_allocator<ArenaType, void>
    {
    public:
        // arena_allocator specific interface
        typedef ArenaType arena_type;

        explicit arena_allocator(arena_type& arena) throw ()
            :   arena_(arena)
            {}
            
    public:
        // ISO/IEC 14882:2003 20.1.5 [lib.allocator.requirements]
        typedef void value_type;
        typedef void* pointer;
        typedef const void* const_pointer;
            
        template<class Other>
        struct rebind
        {
            typedef arena_allocator<arena_type, Other> other;
        };
            
        template <class Other>
        arena_allocator(
            const arena_allocator<arena_type, Other>& other) throw ()
            :   arena_(other.get_arena())
            {
            }
            
    private:
        arena_type& arena_;
    };


    template <
        typename ArenaType,
        typename T>
    class arena_allocator
    {
    public:
        // arena_allocator specific interface
        typedef ArenaType arena_type;
            
        explicit arena_allocator(arena_type& arena) throw ()
            :   arena_(arena)
            {}
            
    public:
        // ISO/IEC 14882:2003 20.1.5 [lib.allocator.requirements]
        typedef std::size_t size_type;
        typedef std::ptrdiff_t difference_type;
        typedef T *pointer;
        typedef const T *const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef T value_type;
            
        template<class Other>
        struct rebind
        {
            typedef arena_allocator<arena_type, Other> other;
        };
            
        template <class Other>
        arena_allocator(
            const arena_allocator<arena_type, Other>& other) throw ()
            :   arena_(other.get_arena())
            {
            }

        pointer address(reference val) const throw ()
            {
                return bits::addressof(val);
            }
            
        const_pointer address(const_reference val) const throw ()
            {
                return bits::addressof(val);
            }
        void construct(pointer ptr, const_reference val)
            {
                new (ptr) value_type(val);
            }

        void destroy(pointer ptr)
            {
                ptr->~value_type();
            }
            
        pointer allocate(
            size_type n,
            void* hint = nullptr)
            {
                return reinterpret_cast<pointer>(
                    arena_->allocate(n * sizeof(value_type)));
            }
            
        void deallocate(pointer ptr, size_type n) throw ()
            {
                arena_->deallocate(ptr, n * sizeof(value_type));
            }
            
        size_type max_size() const throw()
            {
                return arena_->max_bytes() / sizeof(value_type);
            }

    private:
        arena_type& arena_;
    };

    // ISO/IEC 14882:2003 20.1.5 [lib.allocator.requirements]
    template <
        typename ArenaType,
        typename T,
        typename U>
    bool operator==(
        const arena_allocator<ArenaType, T>& lhs,
        const arena_allocator<ArenaType, U>& rhs)
    {
        return
            bits::addressof(lhs.get_arena()) ==
            bits::addressof(rhs.get_arena());
    }
    
    template <
        typename ArenaType,
        typename T,
        typename U>
    bool operator!=(
        const arena_allocator<ArenaType, T>& lhs,
        const arena_allocator<ArenaType, U>& rhs)
    {
        return !(lhs == rhs);
    }

} // namespace manka

#endif // !MANKA__ARENA_ALLOCATOR_HPP
